#!/usr/bin/env bash
set -eo pipefail

# shellcheck source=bin/_config.sh
source "$(dirname "${BASH_SOURCE[0]}")/_config.sh"

function usage_ci() {
    echo -e "Usage: ${1} [OPTIONS] COMMAND

OPTIONS:
  -h, --help   print this message

COMMAND:
  check_configuration  check that the \".circleci/config.yml\" file is up-to-date
  update               generate the \".circleci/config.yml\" file for
                       the current diff as compared to the main branch
"
}

function usage_function() {
    echo -e "usage: ${0} ${1} [-i | --ignore-changelog | ignorechangelog] [-h | --help | help]

${2}

OPTIONS:
  --ignore-changelog  does not include check-changelog job into workflows
  --help              display this help message
"
}


function check_changes() {
    to="${CIRCLE_SHA1:-HEAD}"
    git whatchanged --name-only --pretty="" origin/main.."${to}" | grep "${1}" &> /dev/null
}

# Utility function that updates circle configuration site-factory file according to
# --ignore-changelog flag
# Usage: update_factory ignore-changelog<boolean>
function update_factory() {
    disclaimer="# /!\ DO NOT EDIT: this file is autogenerated"
    template=".circleci/src/workflows/site-factory.yml.tpl"
    
    # Add a workflow for the site from our template
    printf "%s\n" "${disclaimer}" > ".circleci/src/workflows/@site-factory.yml"

    options=""
    if [ "$1" = true ] ; then
        options="--ignore-changelog"
    fi

    sed "s|\${CI-UPDATE-OPTIONS}|$options|;" < $template >> ".circleci/src/workflows/@site-factory.yml"
}

# Utility function that updates circle configuration source files with a
# workflow for each site and running only the necessary jobs
#
# Usage: update_sources ignore-changelog<boolean>
function update_sources() {
    disclaimer="# /!\ DO NOT EDIT: this file is autogenerated"

    while IFS= read -r -d '' site_path
    do
        # Extract site name from path
        site="${site_path//${SITES_DIRECTORY}\//}"

        # Add a workflow for the site from our template
        printf "%s\n" "${disclaimer}" > ".circleci/src/workflows/@${site}.yml"

        # Conditions to run a site's workflow
        if \
            check_changes "^Dockerfile$" || \
            check_changes "^${site_path}/requirements/" || \
            check_changes "^${site_path}/src/" ;
        then
            template=".circleci/src/workflows/site_jobs.yml.tpl"
        else
            template=".circleci/src/workflows/site_no_change.yml.tpl"
        fi

        # Look for custom image name in site's setup.cfg or pyproject.toml file and default to the site name
        image="$site"
        if [[ -f "${site_path}/src/backend/setup.cfg" ]]; then
            image=$(grep "^name = " "${site_path}/src/backend/setup.cfg" | cut -c 8-)
        fi
        if [[ -f "${site_path}/src/backend/pyproject.toml" ]]; then
            image=$(grep "^name = " "${site_path}/src/backend/pyproject.toml" | cut -c 8- | xargs echo)
        fi

        changelog_ignored_branch="main"
        if \
            [ "$1" = true ] && \
            ! check_changes "^${site_path}/CHANGELOG.md" ;
        then
            # If --ignore-changelog is true and site CHANGELOG has not changed
            # check_changelog job will be ignored for this site
            changelog_ignored_branch="/.*/"
        fi

        # shellcheck disable=SC2016
        sed "s|\${SITE}|$site|g;s|\${IMAGE}|$image|g;s|\${CHANGELOG_IGNORED_BRANCH}|$changelog_ignored_branch|g;" \
        < $template >> ".circleci/src/workflows/@${site}.yml"
        
        
    done <  <(find "sites" -maxdepth 1 -mindepth 1 -type d -print0 | sort -z)
}


# Utility function that generates a circle configuration file with a workflow
# for each site and running only the necessary jobs
#
# Usage: update [-i | --ignore-changelog | ignore-changelog] [-h | --help | help]
function update() {
    help="Utility function that generates a circle configuration file with a workflow
for each site and running only the necessary jobs"
    ignore_changelog=false

    while true; do
        case "${1}" in
            -i|--ignore-changelog|ignore-changelog)
                ignore_changelog=true
                break
                ;;
            -h|--help|help)
                usage_function "${FUNCNAME[0]}" "$help"
                exit 0
                ;;
            *)
                break
                ;;
        esac
    done

    update_factory "${ignore_changelog}"
    update_sources "${ignore_changelog}"

    # Generate the `.circleci/config.yml` file from the yaml source tree
    docker run --rm -v "${PROJECT_DIRECTORY}:/data" circleci/circleci-cli:alpine \
       config pack /data/.circleci/src > .circleci/config.yml

    # Check config validity
    docker run --rm -v "${PROJECT_DIRECTORY}:/data" circleci/circleci-cli:alpine \
       config validate /data/.circleci/config.yml
}


# Utility function that checks if the circle configuration committed to git
# corresponds to the state of the project so that the workflows and jobs are
# run according to what has changed as compared to the "main" branch.
#
# Usage: check_configuration [-i | --ignore-changelog | ignore-changelog] [-h | --help | help]
function check_configuration() {
    help="Utility function that checks if the circle configuration committed to git
corresponds to the state of the project so that the workflows and jobs are
run according to what has changed as compared to the \"main\" branch."

    while true; do
        case "${1}" in
            -h|--help|help)
                usage_function "${FUNCNAME[0]}" "$help"
                exit 0
                ;;
            *)
                break
                ;;
        esac
    done

    update "$1"
    if git diff --name-only | grep "^.circleci/"; then
        echo "check: the .circleci configuration does not reflect latest changes."
        exit 1
    fi
}


# Utility function that checks, for a given site, that the tag provided in
# argument is properly set both in the backend and frontend code.
#
# Usage: check_tag SITE TAG
function check_tag() {
    site=$1
    site_path="${SITES_DIRECTORY}/${site}"
    tag=$2

    declare -i fail
    fail=0

    # Check CHANGELOG
    if [[ -n "${tag}" ]] ; then
        # Tags should be of the form "demo-1.2.1" in which "1.2.1" is the version
        if ! [[ "${tag}" =~ ${site}- ]] ; then
            echo "Tags must be of the form \"{site}-{semver version}\"!"
            fail+=1
        fi
        version=${tag//${site}-/}
    else
        version=$(grep -m 1 "## \[[0-9]" "${site_path}/CHANGELOG.md" | \
            cut -d "[" -f2 | cut -d "]" -f1)
    fi

    if ! grep "## \[${version}\] - " "${site_path}/CHANGELOG.md" &> /dev/null ; then
      echo "Version \"${version}\" for \"${site}\" is missing from CHANGELOG!"
      fail+=2
    fi

    # Check page versions
    init_path="${site_path}/src/backend/${site}/__init__.py"
    if ! grep "__version__ = \"${version}\"" "${init_path}" &> /dev/null ; then
      echo "Backend version does not match the current tag!"
      fail+=4
    fi

    package_json="${site_path}/src/frontend/package.json"
    if ! grep "\"version\": \"${version}\"" "${package_json}" &> /dev/null ; then
      echo "Frontend version does not match the current tag!"
      fail+=8
    fi

    # Check dependencies
    richie_version=$(grep "richie==" "${site_path}/requirements/base.txt" | sed s'/richie==//')

    if ! grep "\"richie-education\": \"${richie_version}\"" "${package_json}" &> /dev/null ; then
      echo "Richie version installed in frontend does not match version installed in backend!"
      fail+=16
    fi

    exit ${fail}
}


while true; do
    case "${1}" in
        -h|--help|help)
            usage_ci "${0}"
            exit 0
            ;;
        check_configuration|check_tag|update)
            "$@"
            exit 0
            ;;
        *)
            usage_ci "${0}"
            exit 1
    esac
done
